/*
 * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.engine.test.regression;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.util.HashMap;
import java.util.Map;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * From http://forums.activiti.org/content/inability-completely-delete-process-instance-when
 */
public class DeleteProcessInstanceTest extends PluggableActivitiTestCase {

    private static Logger log = LoggerFactory.getLogger(DeleteProcessInstanceTest.class);

    @Deployment
    public void testNoEndTimeSet() {
        // Note that the instance with a Task Type of "user" is being started.
        log.info("Starting an instance of \"Demo Partial Deletion\" with a Task Type of \"user\".");

        // Set the inputs for the first process instance, which we will be able
        // to completely delete.
        Map<String, Object> inputParamsUser = new HashMap<String, Object>();
        inputParamsUser.put("taskType", "user");

        // Start the process instance & ensure it's started.
        ProcessInstance instanceUser = runtimeService.startProcessInstanceByKey("DemoPartialDeletion", inputParamsUser);
        assertThat(instanceUser).isNotNull();
        log.info(
            "Process instance (of process model " +
            instanceUser.getProcessDefinitionId() +
            ") started with id: " +
            instanceUser.getId() +
            "."
        );

        // Assert that the process instance is active.
        Execution executionUser = runtimeService
            .createExecutionQuery()
            .processInstanceId(instanceUser.getProcessInstanceId())
            .onlyChildExecutions()
            .singleResult();
        assertThat(executionUser.isEnded()).isFalse();

        // Assert that a user task is available for claiming.
        Task taskUser = taskService
            .createTaskQuery()
            .processInstanceId(instanceUser.getProcessInstanceId())
            .singleResult();
        assertThat(taskUser).isNotNull();

        // Delete the process instance.
        runtimeService.deleteProcessInstance(instanceUser.getId(), null);

        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
            // Retrieve the HistoricProcessInstance and assert that there is an
            // end time.
            HistoricProcessInstance hInstanceUser = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(instanceUser.getId())
                .singleResult();
            assertThat(hInstanceUser.getEndTime()).isNotNull();
            log.info(
                "End time for the deleted instance of \"Demo Partial Deletion\" that was started with a Task Type of \"user\": " +
                hInstanceUser.getEndTime() +
                "."
            );
            log.info(
                "Successfully deleted the instance of \"Demo Partial Deletion\" that was started with a Task Type of \"user\"."
            );
        }

        // Note that the instance with a Task Type of "java" is being started.
        log.info("Starting an instance of \"Demo Partial Deletion\" with a Task Type of \"java\".");

        // Set the inputs for the second process instance, which we will NOT be
        // able to completely delete.
        Map<String, Object> inputParamsJava = new HashMap<String, Object>();
        inputParamsJava.put("taskType", "java");

        // Start the process instance & ensure it's started.
        ProcessInstance instanceJava = runtimeService.startProcessInstanceByKey("DemoPartialDeletion", inputParamsJava);
        assertThat(instanceJava).isNotNull();
        log.info(
            "Process instance (of process model " +
            instanceJava.getProcessDefinitionId() +
            ") started with id: " +
            instanceJava.getId() +
            "."
        );

        // Assert that the process instance is active.
        Execution executionJava = runtimeService
            .createExecutionQuery()
            .processInstanceId(instanceJava.getProcessInstanceId())
            .onlyChildExecutions()
            .singleResult();
        assertThat(executionJava.isEnded()).isFalse();

        // Try to execute job 3 times
        Job jobJavaForException = managementService
            .createJobQuery()
            .processInstanceId(instanceJava.getId())
            .singleResult();
        assertThat(jobJavaForException).isNotNull();

        assertThatExceptionOfType(Exception.class).isThrownBy(() ->
            managementService.executeJob(jobJavaForException.getId())
        );

        assertThatExceptionOfType(Exception.class).isThrownBy(() -> {
                managementService.moveTimerToExecutableJob(jobJavaForException.getId());
                managementService.executeJob(jobJavaForException.getId());
            });

        assertThatExceptionOfType(Exception.class).isThrownBy(() -> {
                managementService.moveTimerToExecutableJob(jobJavaForException.getId());
                managementService.executeJob(jobJavaForException.getId());
            });

        // Assert that there is a failed job.
        assertThat(managementService.createTimerJobQuery().processInstanceId(instanceJava.getId()).count()).isEqualTo(
            0
        );
        Job jobJava = managementService
            .createDeadLetterJobQuery()
            .processInstanceId(instanceJava.getId())
            .singleResult();
        assertThat(jobJava).isNotNull();

        // Delete the process instance.
        runtimeService.deleteProcessInstance(instanceJava.getId(), null);

        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
            // Retrieve the HistoricProcessInstance and assert that there is no
            // end time.
            HistoricProcessInstance hInstanceJava = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(instanceJava.getId())
                .singleResult();
            assertThat(hInstanceJava.getEndTime()).isNotNull();
        }
    }
}
